using System;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;

namespace Gameplay.PVE
{
    public class AStarJob : IJob
    {
        [ReadOnly]
        public AStarMapData mapData;
        private const float REVERSE_FIND_MIN_DISTANCE = 3;
        private const float UNIT_BLOCK_RANGE = 1;
        
        public AStarNodeData nodeData;

        public Vector3 startPosition;

        public Vector3 endPosition;
        
        public int pathLength;
        
        public NativeArray<Vector3> path;

        public int unitId;

        public bool isFindPathSuccess = false;
        
        
        
        private NativeArray<int> openHeap;

        private int openHeapLength = 0;

        private Vector2Int startGrid;
        
        private Vector2Int endGrid;

        private NativeArray<int> targetAroundNodeList;

        private int targetAroundNodeListLength;

        public void Execute()
        {
            isFindPathSuccess = false;
            Priority_Queue_Clear();
            var start = startPosition;
            var end = endPosition;
            //判断是否从终点往起点寻路
            bool isReverseFind = Vector3.Distance(start, end) >= REVERSE_FIND_MIN_DISTANCE;
            
            //得到开始和结束点的格子坐标
            var startPos = mapData.GetIndexByPosition(start,true);
            var endPos = mapData.GetIndexByPosition(end,true);
            startGrid = startPos;
            endGrid = endPos;
            
            //初始化格子信息
            int nodeCount = mapData.mapW * mapData.mapH;
            nodeData.Initialize(nodeCount);
            
            //是否起始点和终点在同一个格子
            if (startPos.x == endPos.x && startPos.y == endPos.y)
            {
                //todo 起点和终点一起的情况
                Path_Add(start);
                Path_Add(end);
                isFindPathSuccess = true;
                return;
            }
            
            //终点是阻挡点
            if (IsBlock(endPos))
            {
                //找周围的点
                GetTargetAroundNodeList(endPos);
                endPos = GetNearestAroundNode();
            }
        }

        private bool IsBlock(int x, int y)
        {
            var xDiff = math.abs(x - startGrid.x);
            var yDiff = math.abs(y - startGrid.y);
            if (xDiff <= UNIT_BLOCK_RANGE && yDiff <= UNIT_BLOCK_RANGE)
            {
                return false;
            }
            return mapData.IsBlock(x,y);
        }
        
        private bool IsBlock(Vector2Int pos)
        {
            return IsBlock(pos.x, pos.y);
        }

        //往外阔两圈得到周围可行的点
        private void GetTargetAroundNodeList(Vector2Int center)
        {
            List_Clear(ref targetAroundNodeList, ref targetAroundNodeListLength);
            for (int j = center.x - 2; j <= center.x + 2; j++)
            {
                if (j < 0 || j >= mapData.mapW)
                {
                    continue;
                }
                for (int k = center.y - 2; k <= center.y + 2; k++)
                {
                    if (k < 0 || k >= mapData.mapH)
                    {
                        continue;
                    }
                    
                    if (!IsBlock(j,k))
                    {
                        var nodeIndex = mapData.GetIndexByVector2(j, k);
                        List_Add(ref targetAroundNodeList,ref targetAroundNodeListLength,nodeIndex);
                    }
                }
            }
        }

        private Vector2Int GetNearestAroundNode()
        {
            float distance = float.MaxValue;
            var candidateNodeIndex = -1;
            for (int i = 0; i < targetAroundNodeListLength; i++)
            {
                var index = targetAroundNodeList[i];
                mapData.GetVector2ByIndex(index,out var x,out var y);
                x = x - endGrid.x;
                y = y - endGrid.y;
                var d = x * x + y * y;
                if (d < distance)
                {
                    distance = d;
                    candidateNodeIndex = index;
                }
            }

            return mapData.GetVector2ByIndex(candidateNodeIndex);
        }

        private void List_Add<T>(ref NativeArray<T> list,ref int listLength,T element) where T : struct
        {
            list[listLength] = element;
            listLength = listLength + 1;
        }

        private void List_Clear<T>(ref NativeArray<T> list, ref int listLength) where T : struct
        {
            listLength = 0;
        }
        
        private void Path_Add(Vector3 position)
        {
            path[pathLength] = position;
            pathLength++;
            
        }
        
        private void Path_Clear()
        {
            pathLength = 0;
        }
        
        private void Priority_Queue_Clear()
        {
            openHeapLength = 0;
        }
        private void Priority_Queue_Push(int v)
        {
            openHeap[openHeapLength++] = v;
            Priority_Queue_SiftUp(openHeapLength++);
        }

        private int Priority_Queue_Pop()
        {
            var v = Priority_Queue_Top();
            openHeap[0] = openHeap[--openHeapLength];
            if (openHeapLength > 0) Priority_Queue_SiftDown(0);
            return v;
        }
        
        public int Priority_Queue_Top()
        {
            if (openHeapLength > 0) return openHeap[0];
            throw new InvalidOperationException("优先队列为空");
        }

        
        private void Priority_Queue_SiftUp(int n)
        {
            var v = openHeap[n];
            for (var n2 = n / 2; n > 0 && Priority_Queue_Compare(n,n2) > 0; n = n2, n2 /= 2) openHeap[n] = openHeap[n2];
            openHeap[n] = v;
        }

        private void Priority_Queue_SiftDown(int n)
        {
            var v = openHeap[n];
            for (var n2 = n * 2; n2 < openHeapLength; n = n2, n2 *= 2)
            {
                if (n2 + 1 < openHeapLength && Priority_Queue_Compare(n2 + 1, n2) > 0) n2++;
                if (Priority_Queue_Compare(n, n2) >= 0) break;
                openHeap[n] = openHeap[n2];
            }
            openHeap[n] = v;
        }

        private int Priority_Queue_Compare(int node1, int node2)
        {
            var f1 = nodeData.nodeF[node1];
            var f2 = nodeData.nodeF[node2];
            if (f1 < f2)
                return 1;
            else if (f1 == f2)
                return 0;
            else
                return -1;
        }
    }
}